#! /usr/bin/perl -w
#
# Probe for functional checking of Globus GRAM Gatekeeper
# Copyright (c) 2005 Emir Imamagic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Changes and Modifications
# =========================
# 9-Sep-2005 - Created;
#
# 9-Sep-2006 - Added command execution
#
# 31-Oct-2006 - Added support for VOMS proxy certificate
#
# 26-Mar-2007 - Common libraries; plugin is WLCG-compatible
#
# 29-Mar-2007 - Added default command
#             - Added certificate lifetime metric
#
# 13-May-2007 - Removed proxy check for CertLifetime metric
#             - removed globus-job-run
#
# 15-May-2007 - Added warning and critical thresholds
#
# 28-May-2007 - Updates related to WLCG specification changes
#
# 11-Jan-2008 - Added wrapper for safe execution of shell commands
#
# 15-Sep-2008 - changed sgutils namespace
#
# 11-Mar-2009 - removed CertLifetime, performed by CertLifetime-probe
#
# 27-Nov-2009 - Migrated to Apache 2.0 license
#
# 23-Aug-2010 - Migrated to Nagios::Plugin.

use strict;
use Nagios::Plugin;
use GridMon::sgutils qw($VONAME &checkProxy &processCommand);
use Sys::Syslog;
use TOM::Nagios qw(nagios_debug);

local $SIG{__DIE__}  = \&TOM::Nagios::handle_die;
local $SIG{__WARN__} = \&TOM::Nagios::handle_warn;
openlog("GRAM-probe", "ndelay,pid", "user");
nagios_debug("started");

# Standard variables used in Nagios::Plugin constructor
use constant PROGNAME => "GRAM-probe";
use constant VERSION => '1.12';
use constant DESCRIPTION => 'Probe for functional checking of Globus GRAM Gatekeeper';
use constant EXTRA_DESC => "";
use constant LICENSE => 'This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
   http://www.apache.org/licenses/LICENSE-2.0
Copyright 2009 Emir Imamagic';
use constant SHORTNAME => 'GRAM';
use constant USAGE => "usage: $0 [ -v ] \n";
use constant DEFAULT_PORT => 2119;
use constant DEFAULT_METRIC => 'GRAM-Auth';

sub processErrorOutput ($) {
    my $res = shift;
    my $answer;
    $answer = "GRAM ERROR: ";
    if ($res =~ /(Valid credentials could not be found)/) {
        # This error happens if credenial are not setup properly
        $answer .= lc($1) . "\n";
                        
    } elsif ($res =~ /^\s*(ERROR\:)?\s*(.+?)\s+Syntax/m) {
        $answer .= lc($2) . "\n";
    } else {
        # remove empty lines
        $res =~ s/^\s*\n//g;
        # replace new lines with spaces
        $res =~ s/\n/ /g;
        # remove standard line
        $res =~ s/GRAM Authentication test failure: //g;
        
        $answer .= $res . "\n";
    }

    $answer;
}

sub processErrorOutputCommand ($) {
    my $res = shift;
    $res =~ s/^\s*\n//g;
    $res =~ s/\n/ /g;
    $res;
}

sub createJobString {
    my $command = shift;
    my $args = shift;
    my $opts = shift;
    my $answer;
    my $state = OK;
    
    if (!$command) {
        $answer = "Command must be defined!\n";
        $state = UNKNOWN;
    } else {
        $answer = "&(executable=\"$command\")";
        $answer .= "(arguments=\"$args\")" if ($args);
        $answer .= $opts if ($opts);
    }
    return ($state,$answer);
}

# Local variables
my ($state,$answer,$res);
my ($command,$args,$expect);
my $cmd;
my $serviceurl;

# Create Nagios::Plugin instance
my $plugin = Nagios::Plugin->new (usage => USAGE,
                                  shortname => SHORTNAME,
                                  version => VERSION,
                                  blurb => DESCRIPTION,
                                  extra => EXTRA_DESC,
                                  license => LICENSE,
                                  plugin  => PROGNAME);
# Define additional arguments
$plugin->add_arg(
    spec => 'hostname|H=s',
    help => "H|hostname\n   Name or IP address of host to check.",
    required => 0,
    default => 'localhost'
);
$plugin->add_arg(
    spec => 'port|p=i',
    help => "p|port\n   Port of the service.\n   (default: ".DEFAULT_PORT.")",
    required => 0,
    default => DEFAULT_PORT
);
$plugin->add_arg(
    spec => 'proxy|x=s',
    help => "proxy|x\n   Location of Nagios user's proxy file.\n   (default: /tmp/x509up_u$<)",
    required => 0,
    default => "/tmp/x509up_u$<"
);
$plugin->add_arg(
    spec => 'vo=s',
    help => "vo\n   Virtual organization of user.\n   (default: )",
    required => 0,
);
$plugin->add_arg(
    spec => 'metric|m=s',
    help => "metric|m
   Metric which should be executed.
   Possible values: GRAM-Auth, GRAM-Command
   GRAM-Auth performs simple authentication
   GRAM-Command executes command via GRAM
   (default: GRAM-Auth)",
    required => 0,
    default => DEFAULT_METRIC
);
$plugin->add_arg(
    spec => 'jobmanager|j=s',
    help => "jobmanager|j
   GRAM2 JobManager used for the test.
   (Default: site's default jobmanager)",
    required => 0,
);
$plugin->add_arg(
    spec => 'command=s',
    help => "command
   Command to execute on remote site.
   (Default: /bin/echo time() is used)",
    required => 0
);
$plugin->add_arg(
    spec => 'args=s',
    help => "args
   Argument to be passed to command.
   (Default: output of time())",
    required => 0
);
$plugin->add_arg(
    spec => 'expect|e=s',
    help => "expect|e
   Expected result of the command.
   (Default: output of time())",
    required => 0
);
$plugin->add_arg(
    spec => 'options|o=s',
    help => "options|o
   Options to be passed to globusrun in case of
   Command in RSL format (e.g. (queue=some)...)
   (Default: )",
    required => 0
);

$plugin->getopts;

unless ($plugin->opts->command) {
    my $myTime = time();
    $command = '/bin/echo';
    $args = $myTime;
    $expect = $myTime;
} else {
    $command = $plugin->opts->command;
    $args = $plugin->opts->args;
    $expect = $plugin->opts->expect;
}

$plugin->nagios_die("Metric ".$plugin->opts->metric." is not supported.")
    if ($plugin->opts->metric !~ /^(GRAM-Auth|GRAM-Command)$/);

$VONAME = $plugin->opts->vo if ($plugin->opts->vo);

$serviceurl = $plugin->opts->hostname . ':' . $plugin->opts->port;
$serviceurl .= "/" . $plugin->opts->jobmanager if ($plugin->opts->jobmanager);

my $globusLocation = $ENV{GLOBUS_LOCATION} || "/usr";
$cmd  = "$globusLocation/bin/globusrun";

# Just in case of problems, let's not hang Nagios
local $SIG{ALRM} = sub {
    local $SIG{TERM} = 'IGNORE';
    kill TERM => -$$;
    $plugin->nagios_die("No Answer from Globus Gatekeeper");
};

local $SIG{TERM} = sub {
    local $SIG{TERM} = 'IGNORE';
    kill TERM => -$$;
    $plugin->nagios_die("Plugin received TERM signal.");
};

alarm($plugin->opts->timeout);

($state,$answer,$res) = checkProxy($plugin->opts->proxy);
if ( $state != OK) {
    $plugin->nagios_exit($state, $answer);
}

if ($plugin->opts->metric eq "GRAM-Auth") {
    $state = OK;

    # Authenticate user
    print "COMMAND: $cmd -a -r $serviceurl\n" if ($plugin->opts->verbose);
    ($state, $res) = processCommand ("$cmd -a -r $serviceurl 2>&1");

    #Turn off alarm
    alarm(0);

    if ($state == CRITICAL) {
        print "ERROR: user authentication failed\n" if ($plugin->opts->verbose);
        $answer = processErrorOutput($res);
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
        if ($res =~ /(GRAM Authentication test successful)/) {
            $answer = "$1\n";
            $res = undef;
        }
    }

    $plugin->nagios_exit($state, $answer);
}

if ($plugin->opts->metric eq "GRAM-Command") {
    $state = OK;

    ($state,$answer,$res) = createJobString($command,$args,$plugin->opts->options);
    if ( $state != OK) {
        $plugin->nagios_exit($state,$answer);
    }

	# Execute command on remote machine
	print "COMMAND: $cmd -s -r $serviceurl \"$answer\"\n" if ($plugin->opts->verbose);
    ($state, $res) = processCommand ("$cmd -s -r $serviceurl \"$answer\" 2>&1");

	#Turn off alarm
	alarm(0);

    if ($state == CRITICAL) {
		print "ERROR: executing command failed\n" if ($plugin->opts->verbose);
		$answer = processErrorOutputCommand($res);
    } elsif ($state == UNKNOWN) {
        $answer = $res;
    } else {
        my $expect = $plugin->opts->expect;
		if (defined $expect ) {
			if ($res =~ /$expect/) {
                processErrorOutputCommand($res);

                $answer = "GRAM OK: Command successfully executed. Output is: \"".processErrorOutputCommand($res)."\". Result matches the expected result.\n";
                $res = undef;
			} else {
                $answer = "GRAM ERROR: Command successfully executed, but result doesn't match the expected result. Output is: \"".processErrorOutputCommand($res)."\". Expected output: \"$expect\".\n";
                $state = CRITICAL;
			}
		} else {
			$answer = "GRAM OK: Command successfully executed. Output is: \"".processErrorOutputCommand($res)."\".\n";
            $res = undef;
		}
	}
    $plugin->nagios_exit($state,$answer);
}


